package cn.mehoyo.certificateGeneration.common;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 缓存Map
 * @author llh
 */
public class CacheMap {

    /** 静态共享清理线程 */
    private static final ScheduledExecutorService CLEANER =
            Executors.newSingleThreadScheduledExecutor(r -> {
                Thread t = new Thread(r, "cache-cleaner");
                t.setDaemon(true);
                return t;
            });

    /**
     * 是否已经启动清理线程
     */
    private static final AtomicBoolean CLEANER_STARTED = new AtomicBoolean(false);

    /** 全部实例列表，用于清理线程统一扫描 */
    private static final List<CacheMap> INSTANCES = new CopyOnWriteArrayList<>();

    /** 实例内部缓存 */
    private final Map<String, Object> MAP = new ConcurrentHashMap<>();
    private final ConcurrentSkipListMap<Long, String> EXPIRE_MAP = new ConcurrentSkipListMap<>();
    private final Map<String, Long> REVERSE_MAP = new ConcurrentHashMap<>();

    /** 每个实例是否初始化 */
    private final AtomicBoolean initialized = new AtomicBoolean(false);

    /** 私有构造，禁止直接 new */
    private CacheMap() { }

    /** 初始化并返回实例，第一次初始化启动清理线程 */
    public static CacheMap initialize() {
        CacheMap instance = new CacheMap();
        instance.start();
        return instance;
    }

    /** 启动实例，注册到全局实例列表 */
    private void start() {
        if (!initialized.compareAndSet(false, true)) {
            return;
        }
        INSTANCES.add(this);

        // 全局只启动一次清理线程
        if (CLEANER_STARTED.compareAndSet(false, true)) {
            CLEANER.scheduleAtFixedRate(CacheMap::cleanAll, 1, 1, TimeUnit.SECONDS);
        }
    }

    /** 清理所有实例的过期缓存 */
    private static void cleanAll() {
        long now = System.currentTimeMillis();
        for (CacheMap cache : INSTANCES) {
            ConcurrentNavigableMap<Long, String> expired = cache.EXPIRE_MAP.headMap(now, true);
            if (!expired.isEmpty()) {
                List<String> expiredKeys = new ArrayList<>(expired.values());
                expiredKeys.forEach(k -> {
                    cache.MAP.remove(k);
                    cache.REVERSE_MAP.remove(k);
                });
                expired.clear();
            }
        }
    }

    public void put(String key, Object value, TimeUnit timeUnit, long time) {
        MAP.put(key, value);
        long expireTime = System.currentTimeMillis() + timeUnit.toMillis(time);
        EXPIRE_MAP.put(expireTime, key);
        REVERSE_MAP.put(key, expireTime);
    }

    public void put(String key, Object value) {
        MAP.put(key, value);
    }

    public Object get(String key) {
        return MAP.get(key);
    }

    public void remove(String key) {
        MAP.remove(key);
        Long expireKey = REVERSE_MAP.remove(key);
        if (expireKey != null) {
            EXPIRE_MAP.remove(expireKey);
        }
    }
}
